package flinty.autocomplete.combobox;

import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.accessibility.Accessible;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.basic.ComboPopup;

/**
 * Utility class for decorating given JComboBox
 */
public final class AutoCompleteComboBoxDecorator {
	private static Logger logger = createLogger(AutoCompleteComboBoxDecorator.class);

	/**
	 * 
	 * @param <T>
	 *            is a class of items displayed in combobox
	 * @param comboBox
	 *            combobox to be decorated
	 * @param model
	 *            successor of BaseAutoCompleteComboBoxModel defining
	 *            autocompletion logic
	 * @param factory
	 *            creator of an modified ListCellRenderer given an original
	 *            renderer, can be zero
	 * @param prototype
	 *            a value that is used as a prototype to define preffered size
	 *            of the combobox and so on, can be zero
	 */
	public static <T> void decorate(final JComboBox comboBox,
			final BaseAutoCompleteComboBoxModel<T> model,
			final RendererFactory factory, final ItemWrapper<T> prototype) {
		comboBox.setEditable(true);
		final AutoCompleteComboBoxDocument document = new AutoCompleteComboBoxDocument(
				comboBox, model);
		comboBox.setModel(model);
		model.setDocument(document);
		if (factory != null) {
			comboBox.setRenderer(factory.createRenderer(comboBox.getRenderer()));
		}
		if (prototype != null) {
			comboBox.setPrototypeDisplayValue(prototype);
		}

		final ValueHolder<ItemWrapper<T>> objHolder = new ValueHolder<ItemWrapper<T>>();
		comboBox.addPopupMenuListener(new PopupMenuListener() {

			@Override
			public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
				if (!model.isUpdatePopup()) {
					objHolder.setValue(model.getSelectedItem());
					model.setLastSelected(model.getSelectedItem());
				}
				if (!model.isProcessingEnabled()) {
					model.enableProcessing();
					comboBox.setPopupVisible(false);
					if (model.getSize() > 0 && !comboBox.isPopupVisible()) {
						comboBox.setPopupVisible(true);
					}
				}
				Accessible a = comboBox.getUI().getAccessibleChild(comboBox, 0);

				if (model.getSize() > 0 && a instanceof ComboPopup) {
					ComboPopup comboPopup = (ComboPopup) a;
					JList list = comboPopup.getList();
					logger.fine("list size is " + list.getModel().getSize());
					for (int n = 0; n < list.getModel().getSize(); n++) {
						if (model.getSelectedItem() == list.getModel()
								.getElementAt(n)) {
							Rectangle rect = list.getCellBounds(n, n);
							if (rect == null) {
								throw new IllegalStateException(
										"attempting to access index " + n
												+ " for " + comboBox);
							}
						}
					}

					list.repaint();
				}
				document.selectAllText();
				logger.fine("becomming visible, selected "
						+ model.getSelectedItem());
			}

			@Override
			public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
				if (!model.isUpdatePopup()) {
					logger.fine("resetting data");
					objHolder.setValue(model.getSelectedItem());
					document.setText(model.getSelectedItem() != null ? model
							.getSelectedItem().toString() : null);
					model.resetFilteredData();
					model.fireContentsChanged(model, -1, -1);
				}
			}

			@Override
			public void popupMenuCanceled(PopupMenuEvent e) {
				logger.fine("cancelled");
				model.setSelectedItem(objHolder.getValue());
			}

		});

		comboBox.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				logger.fine(String.format(
						"action event: command = %s, id = %s",
						e.getActionCommand(), e.getID()));
				if (!comboBox.isPopupVisible()) {
					document.setText(model.getSelectedItem() != null ? model
							.getSelectedItem().toString() : null);
				}

			}
		});

	}

	static Logger createLogger(Class<?> cls) {
		Logger res = Logger.getLogger(cls.getName());
		// exception can be thrown in an applet where Logger.setLevel()
		// can can cause Security exception
		try {
			res.setUseParentHandlers(false);
			for (Handler handler : res.getHandlers()) {
				res.removeHandler(handler);
			}
			String prop = System
					.getProperty("com.flinty.autocomplete.loglevel");
			Level level = Level.OFF;
			if (prop != null) {
				level = Level.parse(prop);
			}
			res.setLevel(level);
			ConsoleHandler handler = new ConsoleHandler();
			handler.setLevel(level);
			handler.setFormatter(new SimpleFormatter());
			res.addHandler(handler);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return res;
	}

	public static interface RendererFactory {
		/**
		 * 
		 * @param originalRenderer
		 *            an original renderer of the combobox
		 * @return modified renderer of the combobox
		 */
		ListCellRenderer createRenderer(ListCellRenderer originalRenderer);
	}

	private AutoCompleteComboBoxDecorator() {
		// no-ops
	}
}
